// 硬件平台：mt7688  
// 软件平台：原生sdk MediaTek_ApSoC_SDK_4300_20140916  linux-3.10.14内核
 
// ws2811是单总线接口的led驱动芯片，通过单个GPIO模拟高低时序发出颜色值，芯片收到后控制输出对应PWM改变三路RGB灯的颜色。
 
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/io.h>
 
// 0x0000ff  g
// 0x00ffff
// 0xffff00 y 
static dev_t sys_led_dev;
static struct class* sys_led_class = 0;
static struct cdev* sys_led_cdev = 0;
#define SYS_LED_NAME		"sys_led"

void __iomem *led_pin_reg_base;
 
union _rgb
{
	unsigned int color;
	struct 
	{
		unsigned char r;
		unsigned char g;
		unsigned char b;
	}u_s;
};


#define LED_PIN0_GPIO1_BASE       0x120b1000 /* GPIO1_REG_BASE */
#define LED_BIT_NUM 				0
#define get_data_config_addr(base_addr, offset) (((base_addr) + 0x000) + (1 << ((offset) + 2)))
#define LED_PIN0_GPIO1_DATA_ADDR 	get_data_config_addr(LED_PIN0_GPIO1_BASE, LED_BIT_NUM)

//#define set_led_hight()		1 //*(volatile u32*)(RALINK_REG_PIOSET1) = (1 << (44 - 32))
//#define set_led_low()		0 //*(volatile u32*)(RALINK_REG_PIORESET1) = (1 << (44 - 32))
 
#define set_led_hight() { \
 volatile int val = *(volatile int *)(led_pin_reg_base); \
 val |= 1 << LED_BIT_NUM; \
 *(volatile int *)(led_pin_reg_base) = val; \
}

#define set_led_low() { \
 volatile int val = *(volatile int *)(led_pin_reg_base); \
 val &= ~(1 << LED_BIT_NUM); \
 *(volatile int *)(led_pin_reg_base) = val; \
}

inline void set_out(void)//设置GPIO为输出以便控制WS2811
{
	//set_led_hight();
#if 0	
	unsigned long tmp;
 
	tmp = *(volatile u32 *)(RALINK_REG_GPIOMODE2);
	tmp |= 1;
	*(volatile u32 *)(RALINK_REG_GPIOMODE2) = tmp;
 
	tmp = *(volatile u32*)(RALINK_REG_PIODIR1);
	tmp |= (1 << (44 - 32));
	*(volatile u32*)(RALINK_REG_PIODIR1) = tmp;
#endif	
}
 
static void set_led_1(void)//发出二进制1
{
	set_led_hight();
	//ndelay(800); //850
	//__delay(800);
	udelay(1);
	set_led_low();
	//ndelay(350); // 400
	//__delay(400);
	udelay(1);
}
 
static void set_led_0(void)//发出二进制0
{
	//struct timespec tstart, tend;
	//int diff = 0;
	//tstart = current_kernel_time();
	
	//int ret;
	//u64 t1;
	//u64 t2;
	//unsigned long delay;
	//unsigned long start = jiffies;
	//t1 = get_jiffies_64();
	set_led_hight();
	//ndelay(350);
	//__delay(300);
	set_led_low();
	//ndelay(800);
	//__delay(1000);
	udelay(1);
	//ret = read_current_timer(&delay);
	
	//uint64_t delta = jiffies_to_nsecs(get_jiffies_64());
	//t2 = jiffies; //get_jiffies_64();
	//printk("t1=%ld, t2=%ld %ld, ret=%d, delay=%ld\n", t1, t2, start, ret, delay);
	//printk("t2-t1 = %ld, delta=%ld\n", t2-t1, delta);
	//tend = current_kernel_time();
	//printk("%s, stime: %lds, utime: %ldus\n", __func__, tstart.tv_sec, tstart.tv_nsec);
	//printk("%s, etime: %lds, utime: %ldus\n", __func__, tend.tv_sec, tend.tv_nsec);
	//diff = ((tend.tv_sec * 1000000000 + tend.tv_nsec) - (tstart.tv_sec * 1000000000 + tstart.tv_nsec));
	//printk("%s, diff: %d\n", __func__, diff);
}
 
// >= 50us
static void reset_op(void)//发出Reset信号
{
	//set_led_hight();//首先将总线拉高ndelay
	//ndelay(300);//保持一段时间
	set_led_low();//拉低
	mdelay(10);//至少保持50us，之后芯片等待接收新的数据
}
 
static int sys_led_open(struct inode* in, struct file* fi)
{
	set_out();
	return 0;
}
 
static long sys_led_ioctl(struct file* fi, unsigned int cmd, unsigned long data)
{
	union _rgb rgb;
	int i, num;
	unsigned int ioarg = 0;
	unsigned int fl;
	printk("cmd==%d, data=0x%03x rgb size = %d\n", cmd, data, sizeof(rgb));
	switch (cmd)
	{
		case 0:
			//if (copy_from_user((unsigned int *)&rgb, (unsigned int __user *)data, sizeof(rgb)))//从用户空间获得颜色值
			if(__get_user(ioarg, (unsigned int __user *)data))
			{
				printk("copy from user color is failed\n");
				return -1;
			}
			
			printk("cmd=%d, data=0x%03x, ioarg=0x%04x\n", cmd, data, ioarg);
			rgb.color = ioarg;
			//set_out();
			reset_op();
			local_irq_save(fl);//把当前中断状态保存到flags中，然后禁用当前处理器上的中断发送
			for(num=0; num<1; num++) {
				rgb.color = ioarg;
				for (i = 0; i < 24; ++i)//发出24bit的颜色值
				{
					//printk("color=0x%04x\n", rgb.color);
					if (1 & rgb.color)
						set_led_1();
					else
						set_led_0();
					rgb.color >>= 1;
				}
			}
			local_irq_restore(fl);//中断恢复
		break;
 
		case 1:
			reset_op();
		break;
	}
	
	printk("sys_led_ioctl leave\n");
	return 0;
}
 
static ssize_t sys_led_write(struct file* fi, const char* __user buf, size_t len, loff_t* ff)
{
	union _rgb rgb;
	unsigned int fl;
	int i;
	if (copy_from_user((char*)&rgb, (char*)buf, sizeof(rgb)))
	{
		return -1;
	}
	set_out();
	reset_op();
	local_irq_save(fl);
	for (i = 0; i < 24; ++i)
	{
		if (1 & rgb.color)
			set_led_1();
		else
			set_led_0();
		rgb.color >>= 1;
	}
	local_irq_restore(fl);
	return sizeof(union _rgb);
}
 
static struct file_operations led_file =
{
	.owner = THIS_MODULE,
	.open = sys_led_open,
	.unlocked_ioctl = sys_led_ioctl,
	.write = sys_led_write,
};
static void sys_led_exit(void);
 
static int sys_led_init(void)
{
	int ret;
	unsigned int val = 0;
	led_pin_reg_base = ioremap(LED_PIN0_GPIO1_DATA_ADDR, 4);
	if(led_pin_reg_base == NULL) {
		printk("error : get led pin data reg iomap failed\n");
		return -1;
	}
	
	sys_led_class = class_create(THIS_MODULE, "sys_led_class");
	if (IS_ERR(sys_led_class))
	{
		printk("error : sys led class create failed\n");
		return -1;
	}
 
	ret = alloc_chrdev_region(&sys_led_dev, 0, 1, SYS_LED_NAME);
	if (ret < 0)
	{
		printk("error : get dev number failed\n");
		goto error_exit;
	}
 
	device_create(sys_led_class, NULL, sys_led_dev, NULL, "%s", SYS_LED_NAME);
 
	sys_led_cdev = cdev_alloc();
 
	if (!sys_led_cdev)
	{
		printk("new memory cdev failed\n");
		goto error_exit;
	}
 
	cdev_init(sys_led_cdev, &led_file);
	sys_led_cdev->owner = THIS_MODULE;
	ret = cdev_add(sys_led_cdev, sys_led_dev, 1);
	if (ret < 0)
	{
		printk("sys led cdev add failed\n");
		goto error_exit;
	}
	val = readl(ioremap(0x120C0000, 4));
	printk("%s: 0x120C0000 = 0x%08x\n", __func__, val);
	val = LED_PIN0_GPIO1_DATA_ADDR;
	printk("%s: LED_PIN0_GPIO1_DATA_ADDR = 0x%08x\n", __func__, val);
	val = readl(led_pin_reg_base);
	printk("%s: led = 0x%08x\n", __func__, *(volatile int *)(led_pin_reg_base));
	if(val == 0)
		set_led_hight();
	
	return 0;
error_exit:
	sys_led_exit();
	return -1;
}
 
static void sys_led_exit(void)
{
	if (sys_led_cdev)
	{
		cdev_del(sys_led_cdev);
	}
 
	if (sys_led_class)
	{
		device_destroy(sys_led_class, sys_led_dev);
		class_destroy(sys_led_class);
	}
 
	unregister_chrdev_region(sys_led_dev, 1);
	
	if(led_pin_reg_base)
		iounmap(led_pin_reg_base);
}
 
 
module_init(sys_led_init);
module_exit(sys_led_exit);
 
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("system status led control");
MODULE_AUTHOR("system led");
